using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public class BlockSyntaxAdapter : IMultiplicityConvertible<CommonSyntaxNode>
    {
        
        private BlockSyntax _blockNode;
        private SyntaxNodeInfo _nodeToConvertInThisBlock;
        private ISemanticModel _semanticModel;

        public BlockSyntaxAdapter(BlockSyntax block, ISemanticModel semanticModel, SyntaxNodeInfo nodeInfo)
        {
           

            _semanticModel = semanticModel;
            _nodeToConvertInThisBlock = nodeInfo;
            _blockNode = block;
        }

        public StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName)
        {
            var newBlock = Syntax.Block();
            var allBlockNodes = _blockNode.ChildNodes();

            //Build a new Block by looping through all the blocknodes in the block and replace them if necessary.
            foreach (var blockNode in allBlockNodes)
            {
                //@TEXT:will store replacement rules for the statement (the original statement)
                List<StatementReplacement<CommonSyntaxNode>> replacements = new List<StatementReplacement<CommonSyntaxNode>>();
                //@TEXT:will store statements that will be added before the original statement (Roslyn can not replace a statement with several other statements)
                var preStatements = new List<StatementSyntax>();




                //TODO: Check with reflector what IsEquivalentTo does. Probably it is only a string compare
                //TODO: Noticed that when there are two times the same statements in a row there will be 4 replacements
                //TODO:So far only the firstordefault node is taken. what happens with the other stuff?
                if (_nodeToConvertInThisBlock.Statement.IsEquivalentTo(blockNode))
                {
                    
                    try
                    {
                        var replacement = ConvertNode(newBlock, _nodeToConvertInThisBlock, replacements, newVariableName);
                        if(replacement != null)
                            replacements.Add(replacement);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(string.Format("Could not convert the node: {0}\nWith {1} Converter", _nodeToConvertInThisBlock.ReferencedNode, this.ToString()));
                    }
                    
                    var newStatement = ((StatementSyntax)blockNode);

                    
                    //Then replace the expressions that need to be replaced in the current statement, if there are any replacements
                    
                    if (replacements != null && replacements.Count > 0)
                    {
                        foreach (var replacement in replacements)
                        {
                            //Verify that the oldnode is equivalent to the node in the statement
                            var oldSyntaxNode = ((StatementSyntax)blockNode).DescendantNodesAndSelf().Where(n => n.IsEquivalentTo(replacement.OldSyntaxNode)).First();
                            replacement.OldSyntaxNode = oldSyntaxNode;
                        }

                        var nodesToAddInBlock = new List<StatementSyntax>();
                        //Compute all replacements of the original statement
                        var oldSyntaxNodes = replacements.Select(n => n.OldSyntaxNode).ToList();
                        newStatement = ((StatementSyntax)blockNode).ReplaceNodes(oldSyntaxNodes, computeReplacementNode: (OldNode, newNode) =>
                        {
                            StatementReplacement<CommonSyntaxNode> replacement = replacements.First(n => n.OldSyntaxNode == OldNode);
                            return replacement.NewSyntaxNode;
                        }
                       );
                        //Save the trivia to rearrange it
                        var leadingTrivia = blockNode.GetLeadingTrivia();
                        var trailingTrivia = blockNode.GetTrailingTrivia();
                        newStatement = newStatement.WithLeadingTrivia(Syntax.Space);
                        newStatement = newStatement.WithTrailingTrivia(Syntax.Space);
                        
                        //populate list with all the nodes we want to add
                        foreach (var replacement in replacements)
                            if(replacement.AdditionalStatements != null && replacement.AdditionalStatements.Count > 0)
                                nodesToAddInBlock.AddRange(replacement.AdditionalStatements);
                        nodesToAddInBlock.Add(newStatement);
                        nodesToAddInBlock[0] = nodesToAddInBlock.First().WithLeadingTrivia(leadingTrivia);
                        nodesToAddInBlock[nodesToAddInBlock.Count - 1] = nodesToAddInBlock.Last().WithTrailingTrivia(trailingTrivia);
                        newBlock = newBlock.AddStatements(nodesToAddInBlock.ToArray());
                    }
                    else
                        newBlock = newBlock.AddStatements((StatementSyntax)blockNode);

                }
                else
                {
                    newBlock = newBlock.AddStatements((StatementSyntax)blockNode);
                }
            }
            return new StatementReplacement<CommonSyntaxNode>(_blockNode, newBlock);
        }

        private StatementReplacement<CommonSyntaxNode> ConvertNode(BlockSyntax newBlock, SyntaxNodeInfo nodeToReplace, List<StatementReplacement<CommonSyntaxNode>> statementReplacements,
            string newVariableName)
        {
            var originalStatement = nodeToReplace.Statement;
            var blockNodeFacInstance = BlockNodeFactory.GetConverter(_semanticModel, newBlock, nodeToReplace);
            var statementReplacement = blockNodeFacInstance.ConvertToMutiplicity(newVariableName);
            return statementReplacement;
        }
    }
}
